package hn.sigit.logic.workflow.permits;

import hn.sigit.dao.SigitDAO;
import hn.sigit.dao.hnd.administrative.HND_PermitDAO;
import hn.sigit.dao.hnd.cadastre.HND_LayerDAO;
import hn.sigit.dao.ladm.external.ExtPartyDAO;
import hn.sigit.logic.bpm.ArchiveRepository;
import hn.sigit.logic.bpm.AttachedFileData;
import hn.sigit.logic.general.GeneralHelper;
import hn.sigit.logic.general.ResourceBundleHelper;
import hn.sigit.logic.general.SpatialAnalysisHelper;
import hn.sigit.logic.general.SpatialAnalysisHelper.SpatialAnalysisResult;
import hn.sigit.logic.workflow.WorkflowHelperBase;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_Permit;
import hn.sigit.model.hnd.administrative.HND_PermitObservation;
import hn.sigit.model.hnd.administrative.HND_PermitRuleGroup;
import hn.sigit.model.hnd.administrative.HND_RuleActionType;
import hn.sigit.model.hnd.administrative.HND_SpatialRule;
import hn.sigit.model.hnd.cadastre.HND_BuildingMaterial;
import hn.sigit.model.hnd.cadastre.HND_LandUse;
import hn.sigit.model.hnd.cadastre.HND_Layer;
import hn.sigit.model.hnd.cadastre.HND_LayerType;
import hn.sigit.model.hnd.cadastre.HND_Parcel;
import hn.sigit.model.hnd.cadastre.HND_SpatialZone;
import hn.sigit.model.ladm.administrative.LA_AdministrativeSource;
import hn.sigit.model.ladm.external.ExtParty;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.model.SelectItem;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.bpm.EndTask;
import org.jboss.seam.bpm.BusinessProcess;


public abstract class PermitHelper<P extends HND_Permit> extends WorkflowHelperBase {
	protected P permit;
	protected Long permitId;
	
	private ExtParty party;
	protected HND_PermitRuleGroup permitRuleGroup;

	private String cashiersCode;
	private BigDecimal payment;

	private boolean approveTransaction;

	private List<HND_SpatialRule> restrictingRuleList = new ArrayList<HND_SpatialRule>();
	private List<HND_Layer> buildingLayers;

	protected HND_LandUse selectedLandUse;
	
	protected List<HND_PermitRuleGroup> permitRuleGroupList;
	protected List<SelectItem> permitRuleGroupItemList;
	protected List<HND_PermitObservation> permitObservations;
	
	protected List<HND_SpatialZone> numberOfFloorsRestrictingSZList;
	protected List<HND_SpatialZone> buildingAreaRestrictingSZList;
	protected List<HND_SpatialZone> buildingMaterialRestrictingSZList;
	protected List<HND_SpatialZone> landUseRestrictingSZList;
	
	protected Map<HND_SpatialZone, List<HND_BuildingMaterial>> szBuildingMaterials;
	protected Map<HND_SpatialZone, List<HND_LandUse>> szLandUses;


	@In
	protected ResourceBundleHelper resBundle;
	
	@In
	protected GeneralHelper generalHelper;

	@In
	protected SpatialAnalysisHelper spatialAnalysisHelper;
	
	private PermitRuleGroupConverter permitRuleGroupConverter;
	private boolean renderViewRequestPanel = false;

	
	public abstract P getPermit();
	public abstract void setPermit(P permit);
	
	public abstract HND_PermitRuleGroup getPermitRuleGroup();
	
	
	public Long getPermitId() {
		return permitId;
	}
	public void setPermitId(Long permitId) {
		this.permitId = permitId;
	}
	
	public ExtParty getParty() {
		if (party == null && getPermit() != null)
			party = getPermit().getExtParty();
		return party;
	}
	public void setParty(ExtParty party) {
		if (this.party != party) {
			this.party = party;
			if (party != null && getPermit() != null)
				getPermit().setExtParty(party);
		}
	}
	
	public Long getPartyId() {
		return getParty() != null ? getParty().getExtPID() : null;
	}
	
	public String getPartyNationalIdentity() {
		return getParty() != null ? getParty().getFormalIdentity() : "";
	}
	public void setPartyNationalIdentity(String partyNationalIdentity) {
	}
	
	public String getPartyName() {
		return getParty() != null ? getParty().getName() : "";
	}
	
	public String getSelectedZoneName() {
		return (permit != null && permit.getSelectedZone() != null) ? generalHelper.formatZoneNameText(permit.getSelectedZone()) : null;
	}
	public void setSelectedZoneName(String selectedZoneName) {
	}
	
	public String getCashiersCode() {
		return cashiersCode;
	}
	public void setCashiersCode(String cashiersCode) {
		this.cashiersCode = cashiersCode;
	}
	
	public BigDecimal getPayment() {
		return payment;
	}
	public void setPayment(BigDecimal payment) {
		this.payment = payment;
	}
	
	public boolean isRenderViewRequestPanel() {
		return renderViewRequestPanel;
	}
	public void setRenderViewRequestPanel(boolean renderViewRequestPanel) {
		this.renderViewRequestPanel = renderViewRequestPanel;
	}
	
	public void acceptSelectedFile() {
		if (selectedFile == null) return;
		
		if (permit != null && permit.getId() == null) {
			if ("0".equals(editMode)) { //agregando
				if (null == attachedFiles)
					attachedFiles = new ArrayList<AttachedFileData>();
				attachedFiles.add(selectedFile);
			}
			else if ("1".equals(editMode)) { //editando
				attachedFiles.set(currentRow, selectedFile);
			}
		}
		else if (permit != null) {
			if ("0".equals(editMode)) {
				LA_AdministrativeSource newSource = ArchiveRepository.addToRepository(appOptions, selectedFile);
				permit.getSources().add(newSource);
				setAttachedFiles(null);
			}
			else if ("1".equals(editMode)) {
				LA_AdministrativeSource source = null;
				for (LA_AdministrativeSource src : permit.getSources())
					if (src.getsID() == selectedFile.getId()) {
						source = src;
						break;
					}
				if (source != null) {
					source.setType(selectedFile.getAdminSourceType());
					source.getArchive().setDescription(selectedFile.getDescripcion());
				}
			}
			
			HND_PermitDAO.save(permit);
		}
		
		setSelectedFile(null);
		fileSources = null;
	}

	
	public String hasAttachedFiles() {
		if (attachInfo)
			return "attaches";
		
		return "does_not_attach";
	}
	
	public List<LA_AdministrativeSource> getFileSources() {
		if (fileSources == null) {
			//TODO: we could add an ordering criterion
			fileSources = new ArrayList<LA_AdministrativeSource>();
			if (permit != null && permit.getSources() != null)
				for (LA_AdministrativeSource source : permit.getSources())
					fileSources.add(source);
		}
		
		return fileSources;
	}
	
	public List<HND_PermitObservation> getPermitObservations() {
		if (permitObservations == null) {
			permitObservations = new ArrayList<HND_PermitObservation>();
			if (permit != null && permit.getPermitObservations() != null)
				for (HND_PermitObservation po : permit.getPermitObservations())
					permitObservations.add(po);
		}
		return permitObservations;
	}
	
	public String selectZoneFromMap() {
		ISpatialZone sz = interactiveViewerHelper.getSelectedZone();
		if (permit != null && sz instanceof HND_Parcel) {
			permit.setSelectedZone( (HND_SpatialZone) sz );
		}
		return null;
	}
	
	public boolean isApproveTransaction() {
		return approveTransaction;
	}
	public void setApproveTransaction(boolean approveTransaction) {
		this.approveTransaction = approveTransaction;
	}
	
	@EndTask
	public String endTask() {
		return "home";
	}
	
	@EndTask
	public String endPresentationTask() {
		permit.setReceptionistUserName(actor.getId());
		permit.setReceptionistFullName(loggedUserFullName);
		
		return "home";
	}
	
	@EndTask
	public String endAssessmentsTask() {
		if (!spatialAnalysisHelper.getSpatialRuleToAnalysisMap().isEmpty()) {
			//new spatial analysis was made, so need to clear current observations
			permit.getPermitObservations().clear();
			for (HND_PermitObservation po : getSarToPermitObsMap().values())
				permit.getPermitObservations().add(po);
		}
		
		permit.setAnalystUserName(actor.getId());
		permit.setAnalystFullName(loggedUserFullName);
		
		HND_PermitDAO.save(permit);

		return "home";
	}
	
	public String endApprovalTask() {
		permit.setApproved(approveTransaction);
		permit.setApproverUserName(actor.getId());
		permit.setApproverFullName(loggedUserFullName);
		permit.setIssueDate(new Date());
		
		HND_PermitDAO.save(permit);
		
		BusinessProcess.instance().endTask(null);

		return "home";
	}
	
	@Override
	public String pause(boolean doSave) {
		if (doSave) save();
		return "home";
	}
	
	@Override
	public String save() {
		HND_PermitDAO.save(permit);
		return null;
	}
	
	@EndTask(transition="back_to_assessments")
	public String endApprovalBackToAssessments() {
		permit.setApproverUserName(actor.getId());
		permit.setApproverFullName(loggedUserFullName);

		HND_PermitDAO.save(permit);
		
		return "home";
	}
	
	@Override
	public List<AttachedFileData> getAttachedFiles() {
		if (super.getAttachedFiles() == null) {
			if (getPermit() != null && getPermit().getSources() != null && getPermit().getSources().size() > 0) {
				attachedFiles = new ArrayList<AttachedFileData>();
				for (LA_AdministrativeSource source : getPermit().getSources()) {
					AttachedFileData afd = new AttachedFileData();
					afd.setAdminSourceType(source.getType());
					afd.setId(source.getsID());
					afd.setFileName(source.getArchive().getName());
					afd.setDescripcion(source.getArchive().getDescription());
					
					attachedFiles.add(afd);
				}
			}
		}
		return super.getAttachedFiles();
	}

	protected void beginProcessAux(HND_Permit hndPermit) {
		hndPermit.setRequestDate(new Date());
		hndPermit.setReceptionistUserName(actor.getId());
		hndPermit.setReceptionistFullName(loggedUserFullName);

		ExtParty extParty = ExtPartyDAO.loadExtPartyByExtPID(getPartyId());
		hndPermit.setExtParty(extParty);
		
		List<AttachedFileData> attachedFiles = getAttachedFiles();
		if (attachedFiles != null) {
			Set<LA_AdministrativeSource> adminSources = hndPermit.getSources();
			if (adminSources == null) {
				adminSources = new HashSet<LA_AdministrativeSource>();
				hndPermit.setSources(adminSources);
			}
			
			LA_AdministrativeSource adminSource;
			for (AttachedFileData afd : attachedFiles) {
				adminSource = ArchiveRepository.addToRepository(appOptions, afd);
				adminSources.add(adminSource);
			}
		}
	}

	public List<SelectItem> getPermitRuleGroupItemList() {
		if (permitRuleGroupItemList == null) {
			permitRuleGroupItemList = new ArrayList<SelectItem>();
			permitRuleGroupItemList.add(new SelectItem(null, "(seleccionar)"));
			for (HND_PermitRuleGroup prg : getPermitRuleGroupList())
				permitRuleGroupItemList.add(new SelectItem(prg, prg.getName() + " - " + prg.getDescription()));
		}
		return permitRuleGroupItemList;
	}
	
	public List<HND_SpatialRule> getRuleList() {
		return spatialAnalysisHelper.getRuleGroupRules();
	}
	
	
	/**List used in administrative analysis*/
	public String gotoAdminAnalysis() {
		buildingAreaRestrictingSZList = null;
		buildingMaterialRestrictingSZList = null;
		numberOfFloorsRestrictingSZList = null;
		landUseRestrictingSZList = null;
		szBuildingMaterials = null;
		szLandUses = null;
		
		return "next";
	}
	
	public abstract List<HND_SpatialZone> getLandUseRestrictingSZList();
	public void setLandUseRestrictingSZList(
			List<HND_SpatialZone> landUseRestrictingSZList) {
		this.landUseRestrictingSZList = landUseRestrictingSZList;
	}
	
	public abstract boolean isAdminAnalysisOK();
	
	protected boolean listOK(List<?> list) {
		return list == null || list.isEmpty();
	}
	/**END*/
	
	/**Functions for Spatial Analysis*/
	public String gotoSpatialAnalysis() {
		spatialAnalysisHelper.setSpatialZone(getPermit().getSelectedZone());
		spatialAnalysisHelper.setRuleGroup(getPermitRuleGroup());
		spatialAnalysisHelper.setDistance(null);
		
		Set<HND_LandUse> additionaLUSet = new HashSet<HND_LandUse>();
		additionaLUSet.add(getLandUse());
		additionaLUSet.addAll(getOtherLandUses());
		spatialAnalysisHelper.setLhsAdditionalLUSet(additionaLUSet);
		
		spatialAnalysisHelper.doNewSpatialAnalysis();
		
		return "next";
	}
	
	public boolean isSpatialAnalysisOK() {
		final List<?>[] sarLists = { spatialAnalysisHelper.getDistanceResults(),
				spatialAnalysisHelper.getOverlapsResults(),
				spatialAnalysisHelper.getWithinResults(),
				spatialAnalysisHelper.getTouchesResults(),
				spatialAnalysisHelper.getCrossesResults(),
				spatialAnalysisHelper.getContainsResults()
				};
		
		restrictingRuleList.clear();
		for (List<?> sarList : sarLists)
			for (Object sar : sarList)
				if (sar != null)
					for (HND_SpatialRule sr : ((SpatialAnalysisResult) sar).getSpatialRuleSet())
						if (sr.getAction() == HND_RuleActionType.MINUS)
							if (!restrictingRuleList.contains(sr))
								restrictingRuleList.add(sr);
		
		return restrictingRuleList.size() == 0;
	}
	public List<HND_SpatialRule> getRestrictingRuleList() {
		return restrictingRuleList;
	}
	
	public abstract HND_LandUse getLandUse();
	public abstract List<HND_LandUse> getOtherLandUses();
	/**END*/
	
	public String goBack() {
		return "back";
	}
	
	@EndTask
	public String endAssessments() {
		getPermit().setAnalystUserName(actor.getId());
		getPermit().setAnalystFullName(loggedUserFullName);
		SigitDAO.save(getPermit());
		
		return "home";
	}
	
	@EndTask
	public String endDigitization() {
		getPermit().setEditorUserName(actor.getId());
		getPermit().setEditorFullName(loggedUserFullName);
		SigitDAO.save(getPermit());
		
		return "home";
	}
	
	
	protected boolean areLandUseSetsCompatible(Set<HND_LandUse> permitLandUses, Set<HND_LandUse> szLandUses,
			List<HND_LandUse> szIncompatibleLandUses) {
		boolean retval = true;
		for (HND_LandUse permitLU : permitLandUses)
			for (HND_LandUse szLU : szLandUses)
				if (!generalHelper.areLandUsesCompatible(permitLU, szLU)) {
					if (szIncompatibleLandUses != null)
						szIncompatibleLandUses.add(szLU);
					else
						return false;
					
					retval = false;
				}
		
		return retval;
	}
	
	protected <T> Set<T> listToSet(List<T> list) {
		if (list == null) return null;
		
		Set<T> newSet = new HashSet<T>();
		for (T t : list)
			if (t != null)
				newSet.add(t);
		
		return newSet;
	}
	

	private Map<SpatialAnalysisResult, HND_PermitObservation> getSarToPermitObsMap() {
		Map<SpatialAnalysisResult, HND_PermitObservation> sarToPermitObsMap = new HashMap<SpatialAnalysisResult, HND_PermitObservation>();
		HND_PermitObservation permitObservation;
		for (HND_SpatialRule spatialRule : getRuleList()) {
			List<SpatialAnalysisResult> sarList = spatialAnalysisHelper.getSpatialRuleToAnalysisMap().get(spatialRule);
			for (SpatialAnalysisResult sar : sarList) {
				permitObservation = new HND_PermitObservation();
				permitObservation.setPermit(permit);
				permitObservation.setSpatialRuleName(spatialRule.getCode());
				permitObservation.setSpatialRuleDescription(spatialRule.getDescription());
				permitObservation.setSpatialRuleDetails(generalHelper.spatialRuleDetailsText(spatialRule));
				permitObservation.setAction(spatialRule.getAction());
				permitObservation.setTargetSpatialZone(sar.getRhsSpatialZone());
				permitObservation.setAnalystObservation(sar.getAnalystObservation());

				sarToPermitObsMap.put(sar, permitObservation);
			}
		}
		
		return sarToPermitObsMap;
	}

	
	public Converter getPermitRuleGroupConverter() {
		if (permitRuleGroupConverter == null) {
			permitRuleGroupConverter = new PermitRuleGroupConverter();
		}
		return permitRuleGroupConverter;
	}
	
	public List<HND_Layer> getBuildingLayers() {
		if (buildingLayers == null) {
			buildingLayers = HND_LayerDAO.loadLayersByType(HND_LayerType.BUILDING, true);
			if (buildingLayers == null)
				buildingLayers = new ArrayList<HND_Layer>();
		}
		return buildingLayers;
	}


	public HND_LandUse getSelectedLandUse() {
		return selectedLandUse;
	}
	public void setSelectedLandUse(HND_LandUse selectedLandUse) {
		this.selectedLandUse = selectedLandUse;
	}
	public String getSelectedLandUseName() {
		return generalHelper.landUseName(getLandUse());
	}
	public void setSelectedLandUseName(String selectedLandUseName) {
	}
	public abstract void addLandUse();
	public abstract void deleteLandUse(HND_LandUse landUse);
	
	public abstract List<SelectItem> getPermitTypeValueList();
	public abstract List<HND_PermitRuleGroup> getPermitRuleGroupList();

	
	public class PermitRuleGroupConverter implements Converter {
		@Override
		public Object getAsObject(FacesContext context, UIComponent component,
				String value) {
			for (HND_PermitRuleGroup prg : getPermitRuleGroupList())
				if (String.valueOf(prg.getId()).equals(value))
					return prg;
			return null;
		}

		@Override
		public String getAsString(FacesContext context, UIComponent component,
				Object value) {
			if (value == null) return "";
			return String.valueOf(((HND_PermitRuleGroup) value).getId());
		}
	}
}

